/**
  ******************************************************************************
  * @file    modbus_slave.c
  * @author  ashuai0110
  * @version V2.4
  * @date    2024-09-10
  * @brief   modbus从机程序(RTU ASCII)
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2023-10-27    ashuai0110    完成基本内容
  * 2024-01-02    ashuai0110    修改从组件计时节拍接口获取时基
  * 2024-07-24    ashuai0110    修改帧间隔时间为用户自定义参数
  * 2024-08-31    ashuai0110    修改超时判断逻辑,删除一些assert
  * 2024-09-09    ashuai0110    修改数组为对象形式
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "modbus_slave.h"

/**
 * @addtogroup modbus
 * @{
 */

/**
 * @defgroup modbus_slave modbus_slave
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup modbus_slave_local_macros modbus slave local macros
 * @{
 */

/**
 * @defgroup modbus_slave_check_parameters_validity modbus slave check parameters validity
 * @{
 */
/* modbus从机地址有效性检查 */
#define IS_VALID_MBS_ADDR(x)                                                  \
(       ((x) >= 1u)                    &&                                     \
        ((x) <= 247u))
/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup modbus_slave_local_functions modbus slave local functions
 * @{
 */

/**
  * @brief  追加校验后发送
  *
  * @note   modbus的地址和数据项采用大端序,CRC16校验采用小端序
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @retval None
  */
static void mb_slave_send_with_check(mb_slave_t *_mbs)
{
    uint16_t checkVal;
    
    /* ASCII协议 */
    if(MB_ASCII == _mbs->protocol)
    {
        /* 追加LRC校验结果 */
        checkVal = mb_check_lrc(_mbs->dataBuf, _mbs->dataLen);
        _mbs->dataBuf[_mbs->dataLen++] = checkVal;
        /* HEX转ASCII */
        mb_hex_to_ascii(&_mbs->dataBuf[0], _mbs->dataLen, &_mbs->dataBuf[0]);
        _mbs->dataLen = (_mbs->dataLen << 1) + 3;
        /* 添加帧头帧尾标志符 */
        _mbs->dataBuf[0] = 0x3A;
        _mbs->dataBuf[_mbs->dataLen - 2] = 0x0D;
        _mbs->dataBuf[_mbs->dataLen - 1] = 0x0A;
    } /* RTU协议 */
    else
    {
        /* 追加CRC校验结果 */
        checkVal = mb_check_crc16(_mbs->dataBuf, _mbs->dataLen);
        _mbs->dataBuf[_mbs->dataLen++] = checkVal;
        _mbs->dataBuf[_mbs->dataLen++] = checkVal >> 8;
    }
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave[%d] send: ", ch);
    for(uint16_t i = 0; i < _mbs->dataLen; i++)
    {
        MW_PRINT_FUNC("%02X ", _mbs->dataBuf[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
    _mbs->state = MB_TX_BUSY; /* 切换状态 */
    _mbs->txEnFn(1); /* 开启发送 */
}

/**
  * @brief  从机请求处理
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @retval None
  */
static void mb_slave_reqProcess(mb_slave_t *_mbs)
{
    /* 从协议中计算出寄存器地址和数量 */
    uint16_t reg = (_mbs->dataBuf[MB_REG_ADDR_HI_OFF] << 8) | _mbs->dataBuf[MB_REG_ADDR_LO_OFF];
    uint16_t num = (_mbs->dataBuf[MB_NUM_HI_OFF] << 8) | _mbs->dataBuf[MB_NUM_LO_OFF];
    uint8_t errCodeTmp = MB_ERRNO_OK;
    
    switch(_mbs->dataBuf[MB_CODE_OFF])
    {
        case 0x01: /* 读线圈请求处理 */
            errCodeTmp = mb_slave_req_01h(_mbs, reg, num, &_mbs->dataBuf[MB_RSP_DATA_OFF]);
            /* 计算回复的字节数 */
            _mbs->dataBuf[MB_RSP_BYTE_OFF] = (num + 7) >> 3;
            _mbs->dataLen = 3 + _mbs->dataBuf[MB_RSP_BYTE_OFF];
            break;
        case 0x02: /* 读离散量输入请求处理 */
            errCodeTmp = mb_slave_req_02h(_mbs, reg, num, &_mbs->dataBuf[MB_RSP_DATA_OFF]);
            /* 计算回复的字节数 */
            _mbs->dataBuf[MB_RSP_BYTE_OFF] = (num + 7) >> 3;
            _mbs->dataLen = 3 + _mbs->dataBuf[MB_RSP_BYTE_OFF];
            break;
        case 0x03: /* 读保持寄存器请求处理 */
            errCodeTmp = mb_slave_req_03h(_mbs, reg, num, &_mbs->dataBuf[MB_RSP_DATA_OFF]);
            /* 计算回复的字节数 */
            _mbs->dataBuf[MB_RSP_BYTE_OFF] = num << 1;
            _mbs->dataLen = 3 + _mbs->dataBuf[MB_RSP_BYTE_OFF];
            break;
        case 0x04: /* 读输入寄存器请求处理 */
            errCodeTmp = mb_slave_req_04h(_mbs, reg, num, &_mbs->dataBuf[MB_RSP_DATA_OFF]);
            /* 计算回复的字节数 */
            _mbs->dataBuf[MB_RSP_BYTE_OFF] = num << 1;
            _mbs->dataLen = 3 + _mbs->dataBuf[MB_RSP_BYTE_OFF];
            break;
        case 0x05: /* 写单个线圈请求处理 */
            /* num的计算正好与此请求的数据计算对应 故直接使用 */
            if(0xFF00 == num || 0x0 == num)
            {
                errCodeTmp = mb_slave_req_05h(_mbs, reg, num ? 1 : 0);
                _mbs->dataLen = 6;
            }
            else
            {
                errCodeTmp = MB_ERRNO_DATA;
            }
            break;
        case 0x06: /* 写单个寄存器请求处理 */
            /* num的计算正好与此请求的数据计算对应 故直接使用 */
            errCodeTmp = mb_slave_req_06h(_mbs, reg, num);
            _mbs->dataLen = 6;
            break;
        case 0x0F: /* 写多个线圈请求处理 */
            errCodeTmp = mb_slave_req_0fh(_mbs, reg, num, &_mbs->dataBuf[MB_REQ_MULTI_DATA_OFF]);
            _mbs->dataLen = 6;
            break;
        case 0x10: /* 写多个寄存器请求处理 */
            errCodeTmp = mb_slave_req_10h(_mbs, reg, num, &_mbs->dataBuf[MB_REQ_MULTI_DATA_OFF]);
            _mbs->dataLen = 6;
            break;
        default: /* 无效功能码 */
            errCodeTmp = MB_ERRNO_CODE;
            break;
    }
    /* 有错误则回复错误码 */
    if(MB_ERRNO_OK != errCodeTmp)
    {
        _mbs->dataBuf[MB_CODE_OFF] |= 0x80;
        _mbs->dataBuf[MB_RSP_ERR_CODE_OFF] = errCodeTmp;
        _mbs->dataLen = 3;
    }
    mb_slave_send_with_check(_mbs);
}

/**
 * @}
 */

/**
 * @defgroup modbus_slave_global_functions modbus slave global functions
 * @{
 */

/**
  * @brief  modbus从机初始化
  *
  * @param  _mbs      : modbus从机管理实例
  *
  * @param  slaveAddr : 从站地址 1~247
  *
  * @param  protocol  : 协议类型 @ref modbus_common_protocol
  *   @arg  MB_RTU    : RTU协议
  *   @arg  MB_ASCII  : ASCII协议
  *
  * @param  pBuf      : 接收数据缓存区
  *
  * @param  bufLen    : 接收数据缓存区长度
  *
  * @param  txEnFn    : 从机发送前(串口)准备函数
  *
  * @param  frameIntv : 帧间隔超时时间ms,填0则帧间隔超时时间为0
  *
  * @retval None
  */
void mb_slave_init(mb_slave_t *_mbs, uint8_t slaveAddr, uint8_t protocol, void *pBuf, uint16_t bufLen, modbus_tx_en_t txEnFn, uint32_t frameIntv)
{
    MW_ASSERT_PARAM(IS_VALID_POINTER(_mbs));
    MW_ASSERT_PARAM(IS_VALID_MBS_ADDR(slaveAddr));
    MW_ASSERT_PARAM(IS_VALID_MB_PROTOCOL(protocol));
    MW_ASSERT_PARAM(IS_VALID_POINTER(pBuf));
    MW_ASSERT_PARAM(IS_VALID_POINTER(txEnFn));
    
    /* 初始化各参数 */
    memset(_mbs, 0, sizeof(mb_slave_t));
    _mbs->dataBuf = (uint8_t *)pBuf;
    _mbs->dataBufLen = bufLen;
    _mbs->slaveAddr = slaveAddr;
    _mbs->protocol = protocol;
    _mbs->state = MB_RX_IDLE;
    _mbs->targetFrameIntv = frameIntv;
    _mbs->txEnFn = txEnFn;
}

/**
  * @brief  modbus从机轮询处理
  *
  * @param  _mbs             : modbus从机管理实例
  *
  * @retval uint8_t
  *    @arg MW_RET_OK        : 处理完成
  *    @arg MW_RET_ING       : 进行中
  *    @arg MW_RET_LEN_ERR   : 数据长度错误
  *    @arg MW_RET_CHECK_ERR : 数据校验错误
  *    @arg MW_RET_FRAME_ERR : 数据格式错误
  */
uint8_t mb_slave_poll(mb_slave_t *_mbs)
{
    uint32_t tickTmp;
    uint16_t checkVal, startIndex, stopIndex;
    uint8_t ret = MW_RET_ING; /* 进行中 */
    
    switch(_mbs->state)
    {
        case MB_TX_IDLE: break;
        case MB_TX_BUSY: break;
        case MB_RX_IDLE:
            tickTmp = _mbs->curFrameIntv;
            /* 帧间隔超时判断 */
            if(tickTmp && (mw_tick_get() - tickTmp) > _mbs->targetFrameIntv)
            {
                _mbs->curFrameIntv = 0;
                _mbs->state = MB_RX_BUSY; /* 切换状态 */
#if _MBS_DEBUG
                MW_PRINT_FUNC("modbus slave[%d] recv raw data: ", ch);
                for(uint16_t i = 0; i < _mbs->dataLen; i++)
                {
                    MW_PRINT_FUNC("%02X ", _mbs->dataBuf[i]);
                }
                MW_PRINT_FUNC("\r\n");
#endif
            }
            break;
        case MB_RX_BUSY:
            /* ASCII协议数据检查和转换 */
            if(MB_ASCII == _mbs->protocol)
            {
                /* 寻找起始符0x3A */
                for(startIndex = 0; startIndex < _mbs->dataLen; startIndex++)
                {
                    if(0x3A == _mbs->dataBuf[startIndex]) { break; }
                }
                /* 起始符索引检查 */
                if((_mbs->dataLen - startIndex - 1) < 10)
                {
                    ret = MW_RET_FRAME_ERR; /* 请求数据格式错误 */
                    goto rspErr;
                }
                /* 寻找结束符0x0D 0x0A */
                for(stopIndex = startIndex + 1; stopIndex < _mbs->dataLen; stopIndex += 2)
                {
                    if(0x0D == _mbs->dataBuf[stopIndex] && 0x0A == _mbs->dataBuf[stopIndex + 1]) { break; }
                }
                /* 结束符索引检查 */
                if(stopIndex >= _mbs->dataLen)
                {
                    ret = MW_RET_FRAME_ERR; /* 请求数据格式错误 */
                    goto rspErr;
                }
                _mbs->dataLen = stopIndex - startIndex - 1;
                /* ASCII转HEX */
                mb_ascii_to_hex(&_mbs->dataBuf[startIndex + 1], _mbs->dataLen, &_mbs->dataBuf[0]);
                _mbs->dataLen >>= 1;
#if _MBS_DEBUG
                MW_PRINT_FUNC("modbus slave[%d] convert data: ", ch);
                for(uint16_t i = 0; i < _mbs->dataLen; i++)
                {
                    MW_PRINT_FUNC("%02X ", _mbs->dataBuf[i]);
                }
                MW_PRINT_FUNC("\r\n");
#endif
            }
            /* 请求的从机地址与本站地址不相同 */
            if(_mbs->dataBuf[MB_ADDR_OFF] != _mbs->slaveAddr)
            {
                ret = MW_RET_ING; /* 进行中 */
                goto rspErr;
            }
            /* 请求数据长度判断 */
            if(MB_PDU_SIZE_MIN > _mbs->dataLen)
            {
                ret = MW_RET_LEN_ERR; /* 请求数据长度错误 */
                goto rspErr;
            }
            /* ASCII协议 */
            if(MB_ASCII == _mbs->protocol)
            {
                /* LRC校验判断 */
                checkVal = mb_check_lrc(&_mbs->dataBuf[0], _mbs->dataLen - 1);
                if(checkVal != _mbs->dataBuf[_mbs->dataLen - 1])
                {
                    ret = MW_RET_CHECK_ERR; /* 请求数据校验错误 */
                    goto rspErr;
                }
            } /* RTU协议 */
            else
            {
                /* CRC校验判断 */
                checkVal = mb_check_crc16(&_mbs->dataBuf[0], _mbs->dataLen - 2);
                if(checkVal != ((_mbs->dataBuf[_mbs->dataLen - 2]) | _mbs->dataBuf[_mbs->dataLen - 1] << 8))
                {
                    ret = MW_RET_CHECK_ERR; /* 请求数据校验错误 */
                    goto rspErr;
                }
            }
            _mbs->state = MB_TX_IDLE; /* 切换状态 */
            /* 请求处理 */
            mb_slave_reqProcess(_mbs);
            ret = MW_RET_OK; /* 请求处理完成 */
            break;
    }
    
    return ret;
    
rspErr:
    _mbs->dataLen = 0;
    _mbs->state = MB_RX_IDLE; /* 切换状态 */
    
    return ret;
}

/**
  * @brief  写入数据到接收缓冲区
  *
  * @note   一般加入到(串口)接收中断
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @param  pBuf : 数据缓存区
  *
  * @param  len  : 写入长度byte
  *
  * @retval 返回实际写入的数据数量
  */
uint16_t mb_slave_write_rx_buf(mb_slave_t *_mbs, void *pBuf, uint16_t len)
{
    uint16_t unusedLen;
    
    /* 在RX_IDLE状态下可以接收数据 */
    if(MB_RX_IDLE == _mbs->state)
    {
        /* 计算可写数量 */
        unusedLen = _mbs->dataBufLen - _mbs->dataLen;
        len = (len > unusedLen) ? unusedLen : len;
        
        if(0 < len)
        {
            /* 接续copy数据并记录数量 */
            memcpy((void *)&_mbs->dataBuf[_mbs->dataLen], pBuf, len);
            _mbs->dataLen += len;
            _mbs->curFrameIntv = mw_tick_get(); /* 开启帧间隔超时计数 */
            
            return len;
        }
    }
    
    return 0;
}

/**
  * @brief  读出数据从发送缓冲区
  *
  * @note   一般加入到(串口)发送中断
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @param  pBuf : 数据存储区
  *
  * @param  len  : 读出长度byte
  *
  * @retval 返回实际读出的数据数量
  */
uint16_t mb_slave_read_tx_buf(mb_slave_t *_mbs, void *pBuf, uint16_t len)
{
    uint16_t unreadLen;
    
    /* 在TX_BUSY状态下可以发送数据 */
    if(MB_TX_BUSY == _mbs->state)
    {
        /* 计算可读数量 */
        unreadLen = _mbs->dataLen - _mbs->readLen;
        len = (len > unreadLen) ? unreadLen : len;
        
        if(0 < len)
        {
            /* copy数据至外部存储区并记录数量 */
            memcpy(pBuf, (void *)&_mbs->dataBuf[_mbs->readLen], len);
            _mbs->readLen += len;
            /* 数据被全部copy出去 */
            if(_mbs->dataLen == _mbs->readLen)
            {
                /* 清零相关计数 */
                _mbs->dataLen = 0;
                _mbs->readLen = 0;
                _mbs->state = MB_RX_IDLE; /* 切换状态 */
            }
            
            return len;
        }
    }
    _mbs->txEnFn(0); /* 关闭发送 */
    
    return 0;
}

/**
  * @brief  设置从站地址
  *
  * @note   初始化后仍可修改,通信进行中切勿修改
  *
  * @param  _mbs      : modbus从机管理实例
  *
  * @param  slaveAddr : 从站地址 1~247
  *
  * @retval None
  */
void mb_slave_set_slave_addr(mb_slave_t *_mbs, uint8_t slaveAddr)
{
    MW_ASSERT_PARAM(IS_VALID_MBS_ADDR(slaveAddr));
    
    _mbs->slaveAddr = slaveAddr;
}

/**
  * @brief  获取从站地址
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @retval 从站地址
  */
uint8_t mb_slave_get_slave_addr(mb_slave_t *_mbs)
{
    return _mbs->slaveAddr;
}

/**
  * @brief  设置协议类型
  *
  * @note   初始化后仍可修改,需保持通信协议与设定协议一致,不然无法解析成功
  *
  * @param  _mbs     : modbus从机管理实例
  *
  * @param  protocol : 协议类型 @ref modbus_common_protocol
  *   @arg  MB_RTU   : RTU协议
  *   @arg  MB_ASCII : ASCII协议
  *
  * @retval None
  */
void mb_slave_set_protocol(mb_slave_t *_mbs, uint8_t protocol)
{
    MW_ASSERT_PARAM(IS_VALID_MB_PROTOCOL(protocol));
    
    _mbs->protocol = protocol;
}

/**
  * @brief  获取协议类型
  *
  * @param  _mbs     : modbus从机管理实例
  *
  * @retval 返回协议类型 @ref modbus_common_protocol
  *    @arg MB_RTU   : RTU协议
  *    @arg MB_ASCII : ASCII协议
  */
uint8_t mb_slave_get_protocol(mb_slave_t *_mbs)
{
    return _mbs->protocol;
}

/**
  * @brief  设置帧间隔超时时间
  *
  * @param  _mbs      : modbus从机管理实例
  *
  * @param  frameIntv : 帧间隔超时时间ms
  *
  * @retval None
  */
void mb_slave_set_frame_intv(mb_slave_t *_mbs, uint32_t frameIntv)
{
    _mbs->targetFrameIntv = frameIntv;
}

/**
  * @brief  获取帧间隔超时时间
  *
  * @param  _mbs : modbus从机管理实例
  *
  * @retval 返回帧间隔超时时间ms
  */
uint32_t mb_slave_get_frame_intv(mb_slave_t *_mbs)
{
    return _mbs->targetFrameIntv;
}

/**
  * @brief  读线圈请求处理(0x01)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 线圈数量 1线圈=1bit
  *
  * @param  pData         : 线圈状态(用户需将本地的线圈状态放进去)
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_01h(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_RD_COIL_NUM_MAX */
    if(MB_RD_COIL_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req01h> read coils reg:0x%X, num:%d, we should put the data in parameter pData\r\n", reg, num);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  读离散量输入请求处理(0x02)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 离散量数量 1离散量=1bit
  *
  * @param  pData         : 离散量状态(用户需将本地的离散量状态放进去)
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_02h(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_RD_COIL_NUM_MAX */
    if(MB_RD_COIL_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req02h> read discrete inputs reg:0x%X, num:%d, we should put the data in parameter pData\r\n", reg, num);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  读保持寄存器请求处理(0x03)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 寄存器数量 1寄存器=16bit
  *
  * @param  pData         : 保持寄存器值(用户需将本地的保持寄存器值放进去)
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_03h(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_RD_REG_NUM_MAX */
    if(MB_RD_REG_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req03h> read holding registers reg:0x%X, num:%d, we should put the data in parameter pData\r\n", reg, num);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  读输入寄存器请求处理(0x04)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 寄存器数量 1寄存器=16bit
  *
  * @param  pData         : 输入寄存器值(用户需将本地的输入寄存器值放进去)
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_04h(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_RD_REG_NUM_MAX */
    if(MB_RD_REG_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req04h> read input registers reg:0x%X, num:%d, we should put the data in parameter pData\r\n", reg, num);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  写单个线圈请求处理(0x05)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  bit           : 线圈值 0/1
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  */
__weak uint8_t mb_slave_req_05h(mb_slave_t *_mbs, uint16_t reg, uint8_t bit)
{
    /* 单次写入请求无需数量检查 */
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + 1)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req05h> write single coil reg:0x%X, status:%d\r\n", reg, bit);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  写单个寄存器请求处理(0x06)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  data          : 寄存器值
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  */
__weak uint8_t mb_slave_req_06h(mb_slave_t *_mbs, uint16_t reg, uint16_t data)
{
    /* 单次写入请求无需数量检查 */
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + 1)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req06h> write single register reg:0x%X, data:%X\r\n", reg, data);
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  写多个线圈请求处理(0x0f)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 线圈数量 1线圈=1bit
  *
  * @param  pData         : 线圈状态
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_0fh(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_WR_COIL_NUM_MAX */
    if(MB_WR_COIL_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    uint8_t bytes = (num + 7) >> 3;
    
    MW_PRINT_FUNC("modbus slave req0fh> write multiple coils reg:0x%X, num:%d, data:", reg, num);
    for(uint8_t i = 0; i < bytes; i++)
    {
        MW_PRINT_FUNC("%X ", pData[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
    
    return MB_ERRNO_OK;
}

/**
  * @brief  写多个寄存器请求处理(0x10)
  *
  * @param  _mbs          : modbus从机管理实例
  *
  * @param  reg           : 起始地址
  *
  * @param  num           : 寄存器数量 1寄存器=16bit
  *
  * @param  pData         : 寄存器状态
  *
  * @retval 返回处理结果 @ref modbus_common_errno
  *    @arg MB_ERRNO_OK   : 处理完成
  *    @arg MB_ERRNO_ADDR : 处理错误,返回错误代码02
  *    @arg MB_ERRNO_DATA : 处理错误,返回错误代码03
  */
__weak uint8_t mb_slave_req_10h(mb_slave_t *_mbs, uint16_t reg, uint16_t num, uint8_t *pData)
{
    /* 数量检查 仅为示例用户根据情况修改但不应超过最大限制MB_WR_REG_NUM_MAX */
    if(MB_WR_REG_NUM_MAX < num) { return MB_ERRNO_DATA; }
    /* 地址检查 仅为示例用户根据情况修改 */
    if(0x1000 < (reg + num)) { return MB_ERRNO_ADDR; }
    
#if _MBS_DEBUG
    MW_PRINT_FUNC("modbus slave req10h> write multiple registers reg:0x%X, num:%d, data:", reg, num);
    for(uint8_t i = 0; i < num; i++)
    {
        MW_PRINT_FUNC("%X ", (pData[i << 1] << 8) | pData[(i << 1) + 1]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
    
    return MB_ERRNO_OK;
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
